import { ConstraintType, EventsRes, SceneGraph } from '@amcax/base';
import { calculatePoint } from '@app-cad/common/utils';
import { ctx2 } from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { Vector3 } from 'three-legacy';
import {
  SKETCH_CENTEREVENTS,
  sketchEventCenter,
} from '../../../events/SketchEventCenter';
import CmdBase, { PickType } from '../../CmdBase';
import { Dimension } from '../dimension/Dimension';
import { CenterLineRabbetMode, CenterLineRabbetState } from './types';

interface SnapObject {
  curveId: string;
  pickType: PickType;
  point?: Vector3;
}

export default class CmdInteractor extends CmdBase {
  private readonly firstPointFieldName: 'start' | 'mid' =
    this.mode === 'center-line' ? 'start' : 'mid';

  private currentState = CenterLineRabbetState.START;
  private firstPoint: Vector3;
  private secondPoint: Vector3;
  private radius = 0;
  private centerLineVec: Vector3;
  private offsetVec: Vector3;

  private firstSnapPoint: SnapObject;
  private secondSnapPoint: SnapObject;

  private dimCenterLine = new Dimension();
  private dimRadius = new Dimension();

  private isLoading = false;

  private get centerLineStartPoint() {
    if (this.mode === 'center-line') {
      return this.firstPoint;
    }

    if (this.firstPoint && this.secondPoint) {
      return this.firstPoint.clone().multiplyScalar(2).sub(this.secondPoint);
    } else {
      return null;
    }
  }

  private onMouseMove = (e: EventsRes) => {
    this.setMouseWV(e, true);
    if (this.currentState === CenterLineRabbetState.START) {
      this.firstPoint = this.mouseWV.clone();
      this.createAssistPoint(this.firstPoint, true, 0, true);
    } else if (this.currentState === CenterLineRabbetState.ONE_POINT) {
      this.secondPoint = this.mouseWV.clone();
      this.updateCenterLineDimension();
    } else if (this.currentState === CenterLineRabbetState.TWO_POINTS) {
      this.updateWidthDimension();
    }

    this.preview();
  };

  private onMouseDown = async (e: EventsRes) => {
    const snapPoint =
      this.pickType !== PickType.Null
        ? {
            pickType: this.pickType,
            curveId: this.pickSnapInfo.id,
            point: this.mouseWV.clone(),
          }
        : null;

    if (e.button.onLeft) {
      if (this.currentState === CenterLineRabbetState.START) {
        this.createAssistPoint(this.firstPoint, false, 0, true);
        this.previewLines.line = null;

        this.firstSnapPoint = snapPoint;

        this.currentState = CenterLineRabbetState.ONE_POINT;
      } else if (this.currentState === CenterLineRabbetState.ONE_POINT) {
        this.secondSnapPoint = snapPoint;

        this.currentState = CenterLineRabbetState.TWO_POINTS;
      } else if (this.currentState === CenterLineRabbetState.TWO_POINTS) {
        await this.finishDraw();
      }
    } else if (e.button.onRight && !this.closeRightClick) {
      this.currentState = CenterLineRabbetState.START;

      this.destroy();
    }
  };

  enterKeyDown = async () => {
    if (this.currentState === CenterLineRabbetState.ONE_POINT) {
      this.dimCenterLine.determineDim();

      this.secondPoint = calculatePoint(
        this.centerLineVec.clone(),
        this.dimCenterLine.getDimInfo().value,
        this.firstPoint,
      );
      this.updateCenterLineDimension();

      this.createUpdateLine(
        [this.centerLineStartPoint, this.secondPoint],
        'aline',
        1,
        {
          dashSize: 5,
          gapSize: 3,
        },
      );

      this.currentState = CenterLineRabbetState.TWO_POINTS;

      this.updateWidthDimension();

      this.preview();
    } else if (this.currentState === CenterLineRabbetState.TWO_POINTS) {
      this.dimRadius.determineDim();
      this.radius = this.dimRadius.getDimInfo().value / 2;

      await this.finishDraw();
    }
  };

  constructor(private readonly mode: CenterLineRabbetMode) {
    super();
    this.registerEvents();
  }

  reset() {
    this.currentState = CenterLineRabbetState.START;

    this.firstPoint = null;
    this.secondPoint = null;
    this.isVorH = null;

    this.radius = 0;
    this.centerLineVec = null;
    this.offsetVec = null;

    this.firstSnapPoint = null;
    this.secondSnapPoint = null;

    this.isLoading = false;
  }

  registerEvents() {
    this.dimCenterLine.reset();
    this.dimRadius.reset();

    sketchEventCenter()
      .setState('SketchCenterLineRabbet')
      .on(SKETCH_CENTEREVENTS.POINTERMOVE, this.onMouseMove)
      .on(SKETCH_CENTEREVENTS.POINTERDOWN, this.onMouseDown)
      .on(SKETCH_CENTEREVENTS.KEYUP, this.onKeyUp)
      .addExit(this.destroy.bind(this));
  }

  unregisterEvents() {
    sketchEventCenter()
      .off(SKETCH_CENTEREVENTS.POINTERMOVE, this.onMouseMove)
      .off(SKETCH_CENTEREVENTS.POINTERDOWN, this.onMouseDown)
      .off(SKETCH_CENTEREVENTS.KEYUP, this.onKeyUp)
      .setDefault();
  }

  releaseObjects() {
    this.dimCenterLine.destroy();
    this.dimRadius.destroy();

    this.clearLine();
    this.assisGroup.clear();
    SceneGraph.emptyGroup(this.assisGroup);

    this.css2dLabel = null;

    ctx2.viewer.requestRender();
  }

  private updateCenterLineDimension() {
    this.createAssistPoint(this.secondPoint, true, 1);
    if (this.mode === 'mid-point') {
      this.createAssistPoint(this.centerLineStartPoint, true, 2);
    }

    this.centerLineVec = this.secondPoint
      .clone()
      .sub(this.firstPoint)
      .normalize();
    this.dimCenterLine.dimLine([this.firstPoint, this.secondPoint]);

    this.isVorH = this.isSamePoint(this.firstPoint, this.secondPoint);
    if (this.isVorH) {
      this.creatUpdateLabel(this.secondPoint, this.isVorH);
    } else {
      this.showLabel(false);
    }
  }

  private updateWidthDimension() {
    if (
      this.firstPoint == null ||
      this.centerLineVec == null ||
      this.mouseWV == null
    ) {
      return;
    }
    this.radius = this.mouseWV
      .clone()
      .sub(this.firstPoint)
      .cross(this.centerLineVec)
      .length();
    this.offsetVec = this.centerLineVec
      .clone()
      .cross(new Vector3(0, 0, 1))
      .multiplyScalar(this.radius);

    const arcEdge1 = this.secondPoint.clone().add(this.offsetVec);
    const arcEdge2 = this.secondPoint.clone().sub(this.offsetVec);
    const crossProduct = this.secondPoint
      .clone()
      .sub(this.firstPoint)
      .cross(this.mouseWV.clone().sub(this.secondPoint));
    const isSameSide = crossProduct.z > 0;
    const assistPoint = isSameSide ? arcEdge2 : arcEdge1;

    this.createAssistPoint(assistPoint, true, 3);

    this.dimRadius.dimLine([arcEdge1, arcEdge2]);
  }

  private preview() {
    if (this.currentState === CenterLineRabbetState.START) {
      return;
    }

    if (this.currentState === CenterLineRabbetState.ONE_POINT) {
      this.createUpdateLine(
        [this.centerLineStartPoint, this.secondPoint],
        'aline',
        1,
        {
          dashSize: 5,
          gapSize: 3,
        },
      );
    } else if (this.currentState === CenterLineRabbetState.TWO_POINTS) {
      if (this.offsetVec == null) {
        return;
      }
      const vectors: Vector3[] = [];

      const semiCircle1 = this.createSemiCircle(
        this.centerLineStartPoint,
        this.radius,
        this.offsetVec.clone().multiplyScalar(-1),
      );
      vectors.push(...semiCircle1);

      const semiCircle2 = this.createSemiCircle(
        this.secondPoint,
        this.radius,
        this.offsetVec,
      );
      vectors.push(...semiCircle2);

      vectors.push(
        this.centerLineStartPoint
          .clone()
          .add(this.offsetVec.clone().multiplyScalar(-1)),
      );

      this.createUpdateLine(vectors, 'line');
    }

    ctx2.viewer.requestRender();
  }

  private async finishDraw() {
    if (this.secondPoint == null) {
      return;
    }
    const param = {
      mode: this.mode,
      width: Math.abs(this.radius * 2),
      [this.firstPointFieldName]: [this.firstPoint.x, this.firstPoint.y],
      end: [this.secondPoint.x, this.secondPoint.y],
      constraints: this.getConstraints(),
      autoValue1: !this.dimRadius.addDimension(),
      autoValue2: !this.dimCenterLine.addDimension(),
      curveStyle: this.lineType,
    };
    try {
      if (this.isLoading) {
        return;
      }
      try {
        this.isLoading = true;
        const res = await wsApi.createRabbet(this.sketchId, param);
        await this.render({ added: res.added }, true);
      } finally {
        this.isLoading = false;
      }
    } catch (error) {
      //
    }
    this.reset();
    this.releaseObjects();
  }

  private getConstraints() {
    const getConstraintFromSnapPoint = ([snapPoint, point]: [
      SnapObject,
      Vector3,
    ]) =>
      snapPoint?.curveId ? this.getConstraint(snapPoint, point, true) : null;

    const snapPointPointPairs: [SnapObject, Vector3][] = [
      [this.firstSnapPoint, this.firstPoint],
      [this.secondSnapPoint, this.secondPoint],
    ];

    const constraints = snapPointPointPairs
      .map(getConstraintFromSnapPoint)
      .filter(Boolean);

    if (this.isVorH) {
      constraints.push({
        curve: '',
        target: '' as any,
        type:
          this.isVorH === 'V'
            ? ConstraintType.Vertical
            : ConstraintType.Horizontal,
      });
    }
    return constraints;
  }
}
